import { objectReduce, objectEach } from 'fast-loops'
import { combineMultiRules } from 'fela-tools'
import shallowCompare from 'react-addons-shallow-compare'

import generateDisplayName from './generateDisplayName'
import generateSelectorPrefix from './generateSelectorPrefix'
import hoistStatics from './hoistStatics'

const defaultConfig = {
  pure: true,
}

export default function connectFactory(
  BaseComponent,
  createElement,
  RendererContext,
  ThemeContext
) {
  return function connect(rules, config = {}) {
    const connectConfig = {
      ...defaultConfig,
      ...config,
    }

    return (component) => {
      class EnhancedComponent extends BaseComponent {
        static displayName = generateDisplayName(component)

        static _isFelaComponent = true

        shouldComponentUpdate(nextProps, nextState) {
          if (connectConfig.pure) {
            return shallowCompare(this, nextProps, nextState)
          }
          return true
        }

        render() {
          const { extend, _felaRules, ...otherProps } = this.props

          const allRules = [rules]
          if (_felaRules) {
            allRules.push(_felaRules)
          }
          if (extend) {
            allRules.push(extend)
          }

          const combinedRules = combineMultiRules(...allRules)

          return createElement(
            RendererContext.Consumer,
            undefined,
            (renderer) =>
              createElement(ThemeContext.Consumer, undefined, (theme) => {
                const preparedRules = combinedRules(
                  {
                    ...otherProps,
                    theme,
                  },
                  renderer
                )

                // improve developer experience with monolithic renderer
                if (
                  process.env.NODE_ENV !== 'production' &&
                  renderer.prettySelectors
                ) {
                  const componentName =
                    component.displayName || component.name || ''

                  objectEach(preparedRules, (rule, name) => {
                    rule.selectorPrefix = generateSelectorPrefix(
                      componentName,
                      name
                    )
                  })
                }

                if (component._isFelaComponent) {
                  return createElement(component, {
                    _felaRules: combinedRules,
                    ...otherProps,
                  })
                }

                const styles = objectReduce(
                  preparedRules,
                  (styleMap, rule, name) => {
                    styleMap[name] = renderer.renderRule(rule, {
                      ...otherProps,
                      theme,
                    })

                    return styleMap
                  },
                  {}
                )

                const boundRules = objectReduce(
                  preparedRules,
                  (ruleMap, rule, name) => {
                    ruleMap[name] = (props) =>
                      rule(
                        {
                          theme,
                          ...props,
                        },
                        renderer
                      )

                    return ruleMap
                  },
                  {}
                )

                return createElement(component, {
                  ...otherProps,
                  styles,
                  theme,
                  rules: boundRules,
                })
              })
          )
        }
      }

      return hoistStatics(EnhancedComponent, component)
    }
  }
}
